Generated code - XML Webservices / WCF support
Preface
One hot buzzword of the last couple of years has to be 'webservices', which is short for XML Webservices, i.e.: XML message based services offered through
a normal HTTP based website. As webservices are XML based, the .NET framework uses XmlSerializer to produce XML which reflects the objects returned from
a webmethod and to produce instances of classes from the XML received, for example at the client. This section describes how to use the entity and
entity collection classes of the generated code in a webservice scenario. Every entity and entity collection class implements
IXmlSerializable, which
makes it possible to transparently use these classes with webmethods, without the necessity to first produce XML from them using their WriteXml() methods.
The preferred way to implement webservices and
.NET is by using WCF, or Windows Communication Foundation. LLBLGen Pro entities are fully usable with WCF similar to webservices.
By default, LLBLGen Pro entity classes aren't marked with DataContract and
DataMember attributes. If you want to have these defined, use the LLBLGen
Pro designer's attribute feature to define the DataContract and DataMember
attributes on entities and fields. Define these attributes in the Project
Settings of the LLBLGen Pro designer.
For web services and WCF, which both use the IXmlSerializable implementations of the entity and entity collection classes, LLBLGen Pro uses its own Compact25 format, which is very lightweight and very fast to consume and produce as it contains almost no overhead. See for a description of the Compact25 format:
generated code - Xml support.
Example usage
The example discussed here is pretty simple. It offers a service with three methods: GetCustomer, SaveCustomer and GetCustomers. The client consumes the
service to retrieve a customer to have it edited in a winforms application and saves the changed data back into the database using the webservice, plus it
uses the service to display all customers available.
The service project has references to both generated projects: the database generic and the database specific project. The client only has a reference to the
database generic project, as it uses the service for database specific activity, namely the persistence logic to work with the actual data. Because both
client and service have references to the database generic project, they both can use the same types for the entities, in this case the
CustomerEntity.
The code below uses our low-level API, but you
can also use Linq to LLBLGen Pro or QuerySpec to fetch the entities. The
GetCustomers method returns a non-generic
EntityCollection object
as webservices can't deal with generics.
The service
Below is the service code, simplified. As you can see, the code works directly with entity objects and entity collection objects.
// C#
[WebService(Namespace="http://www.llblgen.com/examples")]
public class CustomerService : System.Web.Services.WebService
{
[WebMethod]
public CustomerEntity GetCustomer(string customerID)
{
CustomerEntity toReturn = new CustomerEntity(customerID);
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
adapter.FetchEntity(toReturn);
return toReturn;
}
}
[WebMethod]
public EntityCollection GetCustomers()
{
EntityCollection customers = new EntityCollection(new CustomerEntityFactory());
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
adapter.FetchEntityCollection(customers, null);
return customers;
}
}
[WebMethod]
public bool SaveCustomer(CustomerEntity toSave)
{
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
return adapter.SaveEntity(toSave);
}
}
}
' VB.NET
<WebService(Namespace="http://www.llblgen.com/examples")> _
Public Class CustomerService
Inherits System.Web.Services.WebService
<WebMethod> _
Public Function GetCustomer(customerID As String) As CustomerEntity
Dim toReturn As New CustomerEntity(customerID)
Dim adapter As New DataAccessAdapter()
Try
adapter.FetchEntity(toReturn)
Return toReturn
Finally
adapter.Dispose()
End Try
End Function
<WebMethod> _
Public Function GetCustomers() As EntityCollection
Dim customers As New EntityCollection(New CustomerEntityFactory())
Dim adapter As New DataAccessAdapter()
Try
adapter.FetchEntityCollection(customers, Nothing)
Return customers
Finally
adapter.Dispose()
End Try
End Function
<WebMethod> _
Public Function SaveCustomer(toSave As CustomerEntity) As Boolean
Dim adapter As New DataAccessAdapter()
Try
Return adapter.SaveEntity(toSave)
Finally
adapter.Dispose()
End Try
End Function
End Class
This code forms the code behind of the .asmx file which forms the service entry point.
Custom Member serialization/deserialization
If you add your own member variables and properties to entity classes, you probably also want these values to be serialized and deserialized into the XML stream. Normally, a custom member exposed as a read/write property is serialized as a string using the ToString() method of the value of the custom property. In
some cases this isn't sufficient and you want to perform your own custom xml serialization/deserialization on the value of this custom property, for example if this custom property represents a complex object.
To signal that the LLBLGen Pro runtime framework has to call custom xml serialization code for a given property, the property has to be annotated with a
CustomXmlSerializationAttribute attribute. When a property is seen with that attribute, LLBLGen Pro will call the entity method
entity.PerformCustomXmlSerialization to produce valid XML for the custom property. Likewise, when deserializing an XML stream into entities, the LLBLGen Pro runtime framework will call, when it runs into a property annotated with a
CustomXmlSerializationAttribute, the method
entity.
PerformCustomXmlDeserialization to deserialize the xml for the property into a valid object. You should override these methods in a partial class of the entity which contains the custom properties.
Custom property serialization/deserialization is a feature of the Compact25 xml format, which is used by Adapter in Webservices/WCF scenarios.
Windows Communication Foundation (WCF) support
To be able to send entities from service to client and back using WCF, you have to define a
ServiceContract. This
ServiceContract defines the types involved in the service. It's recommended to define an interface onto which the
ServiceContract is defined. Both client and service now know which types are involved in the service and no stub classes are created anymore nor necessary.
If you want to have a fixed
DataContract instead, it might be
better to send Data Transfer Objects (DTO)'s over the wire which are more or less dumb buckets with data back and forth
instead of entity class instances. The reason is that a
DataContract can't change however an entity might change over time, which then would violate the
DataContract.
LLBLGen Pro's powerful projection framework can help you with projecting fetched data onto DTO classes to send them over the wire.
If you want to return Entity class instances from a WCF service, use
the LLBLGen Pro designer to add DataContract and DataMember attributes to
entity, fields and navigator elements. The best way to do this is in the
Project Settings. See the LLBLGen Pro designer manual for details.
Below is a small example of a simple WCF service and client. Its main purpose is to illustrate what to do to get LLBLGen Pro generated code working with WCF. You should check the MSDN library for information about WCF, configuration of WCF services and other WCF documentation to get a WCF service up and running in your environment.
Note: |
When sending entities over the wire using WCF, the 'IsNew'
flag is not passed along as it is determinable on the service side
for new entities. If you use the trick where you set the IsNew flag
manually on an entity and then send the entity over the wire to the
service, the IsNew flag is set to false at the service during
deserialization, so you have to set it back to true in that special
case scenario. |
Interface with ServiceContract
Below is the service interface definition with the
ServiceContract. The client code will use this interface to refer to the service and the service will use this interface to implement a common interface for clients to connect to.
// C#
[ServiceContract]
[ServiceKnownType(typeof(CustomerEntity))]
[ServiceKnownType(typeof(EntityCollection))]
public interface IWCFExample
{
[OperationContract]
IEntity2 GetCustomer(string customerID);
[OperationContract]
IEntityCollection2 GetCustomers();
}
' VB.NET
<ServiceContract(), _
ServiceKnownType(GetType(CustomerEntity)), _
ServiceKnownType(GetType(EntityCollection))> _
Public Interface IWCFExample
<OperationContract()> _
Function GetCustomer(customerID As String) As IEntity2
<OperationContract(&)gt; _
Function GetCustomers() As IEntityCollection2
End Interface
Server implementation
Below is the implementation of the IWCFExample interface to be used as a WCF service.
As with the other examples above, the collection is a non-generic variant of
EntityCollection, as WCF services can't deal with generic collection
classes.
// C#
// class to implement the service logic
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class WCFExampleService : IWCFExample
{
public IEntity2 GetCustomer(string customerID)
{
CustomerEntity toReturn = new CustomerEntity(customerID);
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
adapter.FetchEntity(toReturn);
}
return toReturn;
}
public IEntityCollection2 GetCustomers()
{
EntityCollection toReturn = new EntityCollection(new CustomerEntityFactory());
using(DataAccessAdapter adapter = new DataAccessAdapter())
{
adapter.FetchEntityCollection(toReturn, null);
}
return toReturn;
}
}
// class to actually run the service:
public class WCFExampleServerHost
{
public WCFExampleServerHost()
{
WCFExampleService server = new WCFExampleService();
ServiceHost host = new ServiceHost(server);
host.Open();
}
}
' VB.NET
' class to implement the service logic
<ServiceBehavior(InstanceContextMode := InstanceContextMode.Single)> _
Public Class WCFExampleService
Implements IWCFExample
Public Function GetCustomer(customerID As string) As IEntity2 Implements IWCFExample.GetCustomer
Dim toReturn As new CustomerEntity(customerID)
Using adapter As New DataAccessAdapter()
adapter.FetchEntity(toReturn)
End Using
Return toReturn
End Function
Public Function GetCustomers() As IEntityCollection2 Implements IWCFExample.GetCustomers
Dim toReturn As New EntityCollection(New CustomerEntityFactory())
Using adapter As New DataAccessAdapter()
adapter.FetchEntityCollection(toReturn, Nothing)
End Using
Return toReturn
End Function
End Class
' class to actually run the service:
Public Class WCFExampleServerHost
Public Sub New()
Dim server As New WCFExampleService()
Dim host As New ServiceHost(server)
host.Open()
End Sub
End Class
Client usage of the service
Below is the code snippet to consume the service defined above. It illustrates the usage of the service.
// C#
ChannelFactory<IWCFExample> channelFactory =
new ChannelFactory<IWCFExample>("WCFExampleServer");
IWCFExample server = channelFactory.CreateChannel();
// Fetch an entity
IEntity2 c = server.GetCustomer("CHOPS");
// Fetch a collection
IEntityCollection2 customers = serverTest.GetCustomers();
' VB.NET
Dim channelFactory As New ChannelFactory(Of IWCFExample)("WCFExampleServer")
Dim server As IWCFExample = channelFactory.CreateChannel()
' Fetch an entity
Dim c As IEntity2 = server.GetCustomer("CHOPS")
' Fetch a collection
Dim customers As IEntityCollection2 = serverTest.GetCustomers()
Configuration of the service
Below is the serviceModel element of the service config file. The settings below are illustrative and your own production service likely will use different values for various WCF settings. Please consult the WCF documentation in the MSDN library for details on the user elements.
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="RemoteConfig"
closeTimeout="infinite"
openTimeout="infinite"
sendTimeout="infinite"
receiveTimeout="infinite"
maxBufferSize="65536000"
maxReceivedMessageSize="65536000" />
</netTcpBinding>
</bindings>
<services>
<service name="Service.WCFExampleServer">
<endpoint address="" binding="netTcpBinding" name="WCFExampleServer"
bindingConfiguration="RemoteConfig"
contract="Interfaces.IWCFExample" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:6543/WCFExampleServer" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
Configuration of the client
Below is the serviceModel element of the client config file. The settings below are illustrative and your own production service likely will use different values for various WCF settings. Please consult the WCF documentation in the MSDN library for details on the user elements.
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="RemoteConfig"
closeTimeout="infinite"
openTimeout="infinite"
sendTimeout="infinite"
receiveTimeout="infinite"
maxBufferSize="65536000"
maxReceivedMessageSize="65536000" />
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:6543/WCFExampleServer"
name="WCFServer" binding="netTcpBinding"
bindingConfiguration="RemoteConfig"
contract="Interfaces.IWCFExample" />
</client>
</system.serviceModel>